#include <stdlib.h>

#include "com.h"

#define SEG(SEG_PTR) ((Com_seg *) (SEG_PTR))

static inline int
seg_idx(Com_ptr *com)
{
	return com->idx % 10;
}

static inline void
com_inc_ref(Com_ptr *com)
{
	++SEG(com->ptr)->refs[seg_idx(com)];
}

static inline void
com_dec_ref(Com_ptr *com)
{
	--SEG(com->ptr)->refs[seg_idx(com)];
}

static inline void *
com_idx_val(Com_ptr *com)
{
	return SEG(com->ptr)->ptrs[seg_idx(com)];
}


int
com_init(Com_ptr *com, Com_seg **seg, void *val)
{
	int i;
	int j;

	for (i = 0; *seg; seg = &(*seg)->next, ++i)
		for (j = 0; j < SEG_NUM; ++j)
			if (!(*seg)->refs[j]) {
				i += j;
			        goto init_com;
			}

	if (!(*seg = seg_new()))
		return -1;

init_com:
	(*seg)->refs[j] = 1;
	(*seg)->ptrs[j] = val;
	com->idx = i;
	com->ptr = *seg;
	return 0;
}

void
com_init_com(Com_ptr *com1, Com_ptr *com2)
{
	if ((*com1 = *com2).idx < 0)
		return;

	com_inc_ref(com1);
}

void
com_init_idiv(Com_ptr *com, void *val)
{
	com->idx = -1;
	com->ptr = val;
}

void
com_bind(Com_ptr *com1, Com_ptr *com2)
{
	if (com1->idx >= 0)
		com_dec_ref(com1);

	com_init_com(com1, com2);
}

int
com_move(Com_ptr *com, Com_seg **seg, void *val)
{
	int idx_seg = seg_idx(com);
	Com_ptr back_up;

	if (idx_seg == 1) {
		com_set(com, val);
		return 0;
	}

	back_up = *com;

	if (com_init(com, seg, val) < 0)
		return -1;

	if (back_up.idx >= 0)
		com_dec_ref(&back_up);

	return 0;
}

void
com_set(Com_ptr *com, void *val)
{
	if (com->idx < 0)
		com_set_indiv(com, val);

	SEG(com->ptr)->ptrs[seg_idx(com)] = val;
}

void
com_set_indiv(Com_ptr *com, void *val)
{
	if (com->idx >= 0) {
		com_dec_ref(com);
		com->idx = -1;
	}

	com->ptr = val;
}

void *
com_val(Com_ptr *com)
{
	return com->idx < 0 ? com->ptr : com_idx_val(com);
}

Com_seg *
seg_new(void)
{
	return calloc(1, sizeof(Com_seg));
}

void
seg_free(Com_seg *seg)
{
	Com_seg *next;

	for (; seg; seg = next) {
		next = seg->next;
		free(seg);
	}
}


